feat(pdu): add arbitrary feature for structure-aware fuzzing#1272
Conversation
There was a problem hiding this comment.
Pull request overview
This PR wires an optional arbitrary feature across ironrdp-pdu (and cascades it into ironrdp-connector) to enable structure-aware fuzzing by providing arbitrary::Arbitrary impls (mostly via derives) for a broad set of RDP PDU types, plus documentation on how to compile with the feature.
Changes:
- Add
arbitraryfeature plumbing toironrdp-pdu(incl.bitflags/arbitrary) and cascade it throughironrdp-connector. - Add
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]across many PDU structs/enums/bitflags, plus manualArbitraryimpls where needed (ChannelName,LicenseExchangeSequence). - Document how to build/check crates with
--features arbitraryfor fuzzing workflows.
Reviewed changes
Copilot reviewed 87 out of 88 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| fuzz/README.md | Documents how to build/check crates with the arbitrary feature for structured fuzzing. |
| crates/ironrdp-pdu/Cargo.toml | Adds arbitrary feature wiring and optional arbitrary dependency. |
| crates/ironrdp-pdu/README.md | Documents arbitrary (and other) feature flags. |
| crates/ironrdp-pdu/src/gcc/mod.rs | Enables Arbitrary on GCC block/container types. |
| crates/ironrdp-pdu/src/gcc/cluster_data.rs | Adds Arbitrary derives for cluster redirection types. |
| crates/ironrdp-pdu/src/gcc/conference_create.rs | Adds Arbitrary derives for conference create PDUs. |
| crates/ironrdp-pdu/src/gcc/core_data/mod.rs | Adds Arbitrary derive for RdpVersion. |
| crates/ironrdp-pdu/src/gcc/core_data/client.rs | Adds Arbitrary derives for client core data + enums/bitflags. |
| crates/ironrdp-pdu/src/gcc/core_data/server.rs | Adds Arbitrary derives for server core data + bitflags. |
| crates/ironrdp-pdu/src/gcc/message_channel_data.rs | Adds Arbitrary derives for message channel data. |
| crates/ironrdp-pdu/src/gcc/monitor_data.rs | Adds Arbitrary derives for monitor data + flags. |
| crates/ironrdp-pdu/src/gcc/monitor_extended_data.rs | Adds Arbitrary derives for extended monitor info + enums. |
| crates/ironrdp-pdu/src/gcc/multi_transport_channel_data.rs | Adds Arbitrary derives for multi-transport channel GCC block. |
| crates/ironrdp-pdu/src/gcc/network_data.rs | Adds manual Arbitrary for ChannelName and derives for network data types/options. |
| crates/ironrdp-pdu/src/gcc/security_data.rs | Adds Arbitrary derives for security data structs/enums/bitflags. |
| crates/ironrdp-pdu/src/geometry.rs | Adds Arbitrary derives for rectangle types. |
| crates/ironrdp-pdu/src/mcs.rs | Adds Arbitrary derives for MCS PDUs/enums/structs. |
| crates/ironrdp-pdu/src/nego.rs | Adds Arbitrary derives for negotiation flags/data types. |
| crates/ironrdp-pdu/src/pcb.rs | Adds Arbitrary derives for preconnection blob/version types. |
| crates/ironrdp-pdu/src/rdp/mod.rs | Adds Arbitrary derive for ClientInfoPdu. |
| crates/ironrdp-pdu/src/rdp/autodetect.rs | Adds Arbitrary derives for auto-detect request/response enums. |
| crates/ironrdp-pdu/src/rdp/client_info.rs | Adds Arbitrary derives for client info structs/enums/bitflags/builder. |
| crates/ironrdp-pdu/src/rdp/finalization_messages.rs | Adds Arbitrary derives for finalization/control-related PDUs. |
| crates/ironrdp-pdu/src/rdp/headers.rs | Adds Arbitrary derives for slow-path headers + related enums/bitflags. |
| crates/ironrdp-pdu/src/rdp/multitransport.rs | Adds Arbitrary derives for multitransport PDUs/enums. |
| crates/ironrdp-pdu/src/rdp/refresh_rectangle.rs | Adds Arbitrary derive for refresh rectangle PDU. |
| crates/ironrdp-pdu/src/rdp/server_error_info.rs | Adds Arbitrary derives for server error info PDU/enums. |
| crates/ironrdp-pdu/src/rdp/session_info/mod.rs | Adds Arbitrary derives for session info wrapper/types. |
| crates/ironrdp-pdu/src/rdp/session_info/logon_info.rs | Adds Arbitrary derives for logon info structs. |
| crates/ironrdp-pdu/src/rdp/session_info/logon_extended.rs | Adds Arbitrary derives for extended logon info + flags/enums. |
| crates/ironrdp-pdu/src/rdp/suppress_output.rs | Adds Arbitrary derives for suppress output PDU + enum. |
| crates/ironrdp-pdu/src/rdp/vc/mod.rs | Adds Arbitrary derives for VC header + control flags. |
| crates/ironrdp-pdu/src/rdp/vc/dvc/gfx/mod.rs | Adds Arbitrary derives for GFX DVC PDUs/types. |
| crates/ironrdp-pdu/src/rdp/vc/dvc/gfx/graphics_messages/mod.rs | Adds Arbitrary derives for GFX capability set + flags and basic types. |
| crates/ironrdp-pdu/src/rdp/vc/dvc/gfx/graphics_messages/client.rs | Adds Arbitrary derives for GFX client PDUs/types. |
| crates/ironrdp-pdu/src/rdp/vc/dvc/gfx/graphics_messages/server.rs | Adds Arbitrary derives for many GFX server message PDUs/enums. |
| crates/ironrdp-pdu/src/rdp/vc/dvc/gfx/graphics_messages/avc_messages.rs | Adds Arbitrary derives for GFX AVC message types/flags. |
| crates/ironrdp-pdu/src/rdp/capability_sets/mod.rs | Adds Arbitrary derives for capability set containers/enums. |
| crates/ironrdp-pdu/src/rdp/capability_sets/general/mod.rs | Adds Arbitrary derives for general capability structs/flags. |
| crates/ironrdp-pdu/src/rdp/capability_sets/bitmap.rs | Adds Arbitrary derives for bitmap capability + flags. |
| crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_cache/mod.rs | Adds Arbitrary derives for bitmap cache capability types/flags. |
| crates/ironrdp-pdu/src/rdp/capability_sets/bitmap_codecs/mod.rs | Adds Arbitrary derives for bitmap codecs capability types/flags/enums. |
| crates/ironrdp-pdu/src/rdp/capability_sets/brush/mod.rs | Adds Arbitrary derives for brush capability types. |
| crates/ironrdp-pdu/src/rdp/capability_sets/frame_acknowledge.rs | Adds Arbitrary derive for frame acknowledge capability. |
| crates/ironrdp-pdu/src/rdp/capability_sets/glyph_cache/mod.rs | Adds Arbitrary derives for glyph cache capability types. |
| crates/ironrdp-pdu/src/rdp/capability_sets/input.rs | Adds Arbitrary derives for input capability + flags. |
| crates/ironrdp-pdu/src/rdp/capability_sets/large_pointer.rs | Adds Arbitrary derives for large pointer capability + flags. |
| crates/ironrdp-pdu/src/rdp/capability_sets/multifragment_update.rs | Adds Arbitrary derive for multifragment update capability. |
| crates/ironrdp-pdu/src/rdp/capability_sets/offscreen_bitmap_cache/mod.rs | Adds Arbitrary derive for offscreen bitmap cache capability. |
| crates/ironrdp-pdu/src/rdp/capability_sets/order/mod.rs | Adds Arbitrary derives for order capability types/flags. |
| crates/ironrdp-pdu/src/rdp/capability_sets/pointer.rs | Adds Arbitrary derive for pointer capability. |
| crates/ironrdp-pdu/src/rdp/capability_sets/sound/mod.rs | Adds Arbitrary derives for sound capability + flags. |
| crates/ironrdp-pdu/src/rdp/capability_sets/surface_commands.rs | Adds Arbitrary derives for surface commands capability + flags. |
| crates/ironrdp-pdu/src/rdp/capability_sets/virtual_channel/mod.rs | Adds Arbitrary derives for virtual channel capability + flags. |
| crates/ironrdp-pdu/src/rdp/server_license/mod.rs | Adds Arbitrary derives for licensing PDUs/types; documents why some errors aren’t annotated. |
| crates/ironrdp-pdu/src/rdp/server_license/licensing_error_message/mod.rs | Adds Arbitrary derives for licensing error message PDU/enums. |
| crates/ironrdp-pdu/src/rdp/server_license/client_new_license_request/mod.rs | Adds Arbitrary derives for new license request PDU + flags. |
| crates/ironrdp-pdu/src/rdp/server_license/client_license_info.rs | Adds Arbitrary derive for client license info PDU. |
| crates/ironrdp-pdu/src/rdp/server_license/client_platform_challenge_response/mod.rs | Adds Arbitrary derives for platform challenge response PDU/enums. |
| crates/ironrdp-pdu/src/rdp/server_license/server_license_request/mod.rs | Adds Arbitrary derives for server license request + subtypes. |
| crates/ironrdp-pdu/src/rdp/server_license/server_license_request/cert.rs | Adds Arbitrary derives for certificate-related types. |
| crates/ironrdp-pdu/src/rdp/server_license/server_platform_challenge/mod.rs | Adds Arbitrary derive for server platform challenge PDU. |
| crates/ironrdp-pdu/src/rdp/server_license/server_upgrade_license/mod.rs | Adds Arbitrary derives for server upgrade license types. |
| crates/ironrdp-pdu/src/input/mod.rs | Adds Arbitrary derives for input event PDU + event enum. |
| crates/ironrdp-pdu/src/input/fast_path.rs | Adds Arbitrary derives for fast-path input types/flags (notably FastPathInput). |
| crates/ironrdp-pdu/src/input/mouse.rs | Adds Arbitrary derives for mouse input types/flags. |
| crates/ironrdp-pdu/src/input/mouse_rel.rs | Adds Arbitrary derives for relative mouse input types/flags. |
| crates/ironrdp-pdu/src/input/mouse_x.rs | Adds Arbitrary derives for extended mouse input types/flags. |
| crates/ironrdp-pdu/src/input/scan_code.rs | Adds Arbitrary derives for scan code input types/flags. |
| crates/ironrdp-pdu/src/input/sync.rs | Adds Arbitrary derives for sync input types/flags. |
| crates/ironrdp-pdu/src/input/unicode.rs | Adds Arbitrary derives for unicode input types/flags. |
| crates/ironrdp-pdu/src/input/unused.rs | Adds Arbitrary derive for unused input PDU. |
| crates/ironrdp-pdu/src/basic_output/fast_path/mod.rs | Adds Arbitrary derives for fast-path output header/update types/flags. |
| crates/ironrdp-pdu/src/basic_output/slow_path.rs | Adds Arbitrary derives for slow-path update enums. |
| crates/ironrdp-pdu/src/basic_output/pointer/mod.rs | Adds Arbitrary derives for pointer update data structures. |
| crates/ironrdp-pdu/src/basic_output/surface_commands/mod.rs | Adds Arbitrary derives for surface command PDUs/enums. |
| crates/ironrdp-pdu/src/basic_output/bitmap/mod.rs | Adds Arbitrary derives for bitmap update structures + flags. |
| crates/ironrdp-pdu/src/basic_output/bitmap/rdp6.rs | Adds Arbitrary derives for RDP6 bitmap stream types. |
| crates/ironrdp-pdu/src/codecs/rfx/mod.rs | Adds Arbitrary derives for RFX codec channel/block types. |
| crates/ironrdp-pdu/src/codecs/rfx/header_messages.rs | Adds Arbitrary derives for RFX header message types. |
| crates/ironrdp-pdu/src/codecs/rfx/data_messages.rs | Adds Arbitrary derives for RFX data message types/flags/enums. |
| crates/ironrdp-pdu/src/codecs/rfx/progressive.rs | Adds Arbitrary derives for RFX progressive message types. |
| crates/ironrdp-connector/Cargo.toml | Makes connector arbitrary feature cascade into ironrdp-pdu/arbitrary. |
| crates/ironrdp-connector/src/lib.rs | Adds Arbitrary derives to several public connector types; documents excluded error enum. |
| crates/ironrdp-connector/src/license_exchange.rs | Adds a manual Arbitrary impl for LicenseExchangeSequence due to trait object field. |
| crates/ironrdp-connector/src/connection.rs | Skips StaticChannelSet during Arbitrary derivation via #[arbitrary(default)]. |
| crates/ironrdp-connector/src/connection_activation.rs | Adds Arbitrary derives for activation sequence/state. |
| Cargo.lock | Updates lockfile due to new optional dependency/feature usage. |
Comments suppressed due to low confidence (1)
crates/ironrdp-pdu/src/input/fast_path.rs:265
- Deriving
arbitrary::ArbitraryforFastPathInputcan generate instances that violate its documented invariant (Vec length must be 1..=255). That invariant is relied on viau8::try_from(self.0.len()).expect(...)inencode()/size(), so fuzz-generated values can trigger panics unrelated to protocol logic. Consider implementing a customArbitraryimpl (or using derive customization) that constrains the event count to 1..=255 (and ideally avoids the empty case) soencode/sizeremain panic-free under--features arbitrary.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
I think I would drop Arbitrary from state machine types for now: ClientConnector, ClientConnectorState, ConnectionActivationSequence, ConnectionActivationState (connector, not PDU).
These are orchestration types, and the core problem is that both *State::Consumed variants are freely generated. Any fuzzer iteration landing in Consumed immediately gets back "connector sequence state is finalized or consumed (this is a bug)". We could use #[arbitrary(skip)], but I think we’re better off with waiting until we implement a good harness at which point it will be easier to design something relevant there.
|
Fixed. The bounded-collection invariant pattern recurs in two more places in this PR's cascade, so the fix is broader than just
Other |
|
OK. Dropped derives on |
Wires the `arbitrary` feature on `ironrdp-pdu` and adjusts the existing `ironrdp-connector` cascade so it composes correctly. This unblocks structure-aware fuzz harnesses that generate valid-looking PDU inputs rather than raw bytes. `ironrdp-pdu`: - `arbitrary = ["alloc", "dep:arbitrary", "bitflags/arbitrary"]` in the feature table, with `bitflags = "2.11"` clean in the dependency table so the cascade is truly optional. - `#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]` on PDU structs and enums across gcc, mcs, nego, capability_sets, client_info, server_license, autodetect, finalization_messages, session_info, suppress_output, refresh_rectangle, server_error_info, multitransport, headers, input, basic_output, codecs/rfx, vc/dvc/gfx. - Hand-rolled `Arbitrary` for `ChannelName` to preserve the null-terminated 8-byte wire-format invariant. - `ServerLicenseError` and similar error enums carrying foreign types (`io::Error`, `FromUtf8Error`, `x509_cert::der::Error`, `PduError`) are intentionally not annotated, with inline comments noting the reason. `ironrdp-connector`: - Cascade adjusted to `arbitrary = ["dep:arbitrary", "ironrdp-pdu/arbitrary"]`. - `static_channels` on `ConnectionResult` and `ClientConnector` skipped via `#[cfg_attr(feature = "arbitrary", arbitrary(default))]` with inline rationale (StaticChannelSet is keyed by TypeId). - `license_cache: Option<Arc<dyn LicenseCache>>` on `Config` skipped similarly with `arbitrary(default)`. - `LicenseExchangeSequence` hand-rolled with `NoopLicenseCache` placeholder so the license-exchange surface stays reachable under fuzz, with inline note that cache-dependent paths are not exercised. - `ConnectorErrorKind` intentionally not annotated (carries `sspi::Error`). Documentation: - `fuzz/README.md` gains a section on building with the `arbitrary` feature and the categories of types that opt out. - `crates/ironrdp-pdu/README.md` gains a `## Feature Flags` section. Verification matrix: - `cargo fmt --all -- --check` clean - `cargo check -p ironrdp-pdu --features arbitrary` succeeds - `cargo check -p ironrdp-pdu --features arbitrary,std` succeeds - `cargo check -p ironrdp-pdu --no-default-features --features arbitrary,alloc` succeeds - `cargo check -p ironrdp-pdu` (default features) succeeds - `cargo check -p ironrdp-connector --features arbitrary` succeeds - `cargo xtask check fmt` clean - `cargo xtask check lints` clean (warnings are pre-existing duplicates) - `cargo xtask check typos` clean Closes Devolutions#1121.
f267d58 to
5d521e7
Compare
af11df1
into
Devolutions:master
|
I opened a follow up PR: #1272 |
Summary
Closes #1121. Part of Benoît Cortier (@CBenoit)'s fuzzing umbrella #1120; this PR unblocks the downstream sub-issues #1122 (Arbitrary impls for more PDU structs), #1123 (CI feature matrix), and #1124 (advanced fuzz targets). Supersedes #1128 by rajatdahal (@razzat008) (stalled 75+ days; 2026-05-11 collaboration offer did not get a response).
Wires the
arbitraryfeature onironrdp-pduand adjusts the existingironrdp-connectorcascade so it composes correctly. This is the foundation for structure-aware fuzzing harnesses that generate valid-looking PDU inputs rather than raw bytes.rajatdahal (@razzat008)'s exploration in #1128 was the starting point. The decisions in this PR about the
ChannelNamemanual impl shape, theArc<dyn LicenseCache>trade-off, theStaticChannelSetskip, and the feature-cascade wiring all build on the questions raised in that thread.Approach
The Cargo.toml wiring uses the explicit cascade pattern:
This keeps
bitflagsitself free of an always-onarbitraryfeature so consumers that do not opt into ourarbitraryfeature do not pull thearbitrarycrate transitively. The cascade preserves theno_std + allocbuild path the issue acceptance criterion calls for.#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]is applied to PDU structs and enums across the gcc, mcs, nego, capability sets, client_info, server_license, autodetect, finalization_messages, session_info, suppress_output, refresh_rectangle, server_error_info, multitransport, headers, input, basic_output, codecs/rfx, and vc/dvc/gfx modules.ChannelNamegets a hand-rolledArbitraryimpl that preserves the wire-format invariant (8-byte null-terminated, up to seven ANSI characters).LicenseExchangeSequenceinironrdp-connectorgets a hand-rolled impl withNoopLicenseCacheas a placeholder so the license-exchange surface stays reachable under fuzz.Alternatives considered
bitflags/arbitrary(as in Fix/arbitrary failing on feature flag #1128): pulls thearbitrarycrate into every consumer ofironrdp-pduunconditionally. Rejected because it defeats the optional-feature design.arbitraryfeature onironrdp-svc(as in Fix/arbitrary failing on feature flag #1128): rejected per the review comment that "we're probably not going to deal with 'arbitrary static virtual channels'." The trait-object types on the connector side are skipped via#[arbitrary(default)]instead.StaticChannelSetwith plausible channels under fuzz: considered but deferred. The skip path matches Fixarbitraryfeature flag plumbing so it compiles #1121's compile-gate scope; channel-state fuzzing coverage is a follow-up evaluated after the initial fuzz runs surface what coverage looks like.Trade-offs accepted
StaticChannelSetfields onConnectionResultandClientConnectorare empty under fuzz (skip via#[arbitrary(default)]). Connector code paths that branch on populated channel state are not exercised. Tracked for coverage iteration.LicenseExchangeSequenceusesNoopLicenseCacheunder fuzz. Cache-dependent paths are not exercised. A fuzz-modeLicenseCacheimpl is a possible follow-up.ServerLicenseError,ConnectorErrorKind) are intentionally not annotated. They are not part of the wire-protocol surface fuzzing targets.Each trade-off is recorded with an inline source comment naming the reason.
Verification
cargo fmt --all -- --checkcleancargo check -p ironrdp-pdu --features arbitrarysucceedscargo check -p ironrdp-pdu --features arbitrary,stdsucceedscargo check -p ironrdp-pdu --no-default-features --features arbitrary,allocsucceedscargo check -p ironrdp-pdu(default features) succeedscargo check -p ironrdp-connector --features arbitrarysucceedscargo xtask check fmtcleancargo xtask check lintsclean (pre-existing warnings unchanged)cargo xtask check typoscleanReferences
arbitraryfeature flag plumbing so it compiles #1121arbitrary#1120 fuzzing umbrella; unblocks ImplementArbitraryfor more PDU structs #1122, CI: compile/test a feature matrix includingarbitrary(std/no_std) #1123, Add advanced fuzz targets to verify more invariants (round-trip, framing, state-machine…) #1124